#include "ClassBuilder.h"

#include "ScriptCompiler/ASTBuilder/AccessLevelBuilder.h"
#include "ScriptCompiler/ASTBuilder/DecoratorBuilder.h"
#include "ScriptCompiler/ASTBuilder/TypeBuilder.h"
#include "ScriptCompiler/ASTBuilder/DelegateBuilder.h"
#include "ScriptCompiler/ASTBuilder/EnumBuilder.h"
#include "ScriptCompiler/ASTBuilder/UsingBuilder.h"
#include "ScriptCompiler/ASTBuilder/FunctionBuilder.h"
#include "ScriptCompiler/ASTBuilder/OperatorFunctionBuilder.h"
#include "ScriptCompiler/ASTBuilder/FieldBuilder.h"
#include "ScriptCompiler/ASTBuilder/PropertyBuilder.h"
#include "ScriptCompiler/ASTBuilder/ConstructorBuilder.h"

#include "ScriptCompiler/AST/ClassNode.h"
#include "ScriptCompiler/AST/AccessLevelNode.h"
#include "ScriptCompiler/AST/DecoratorNode.h"
#include "ScriptCompiler/AST/DecoratorNode.h"
#include "ScriptCompiler/AST/ConstructorNode.h"



#include "ScriptCompiler/ScriptFileContext.h"

CClassBuilder::CClassBuilder(std::shared_ptr<CScriptFileContext> InContext)
{
    Context = InContext;
    Result = std::make_shared<CClassNode>(Context);
}

bool CClassBuilder::ReadCurentToken()
{
    //read class keyword
    {
        auto Token = Context->GetCurrentToken();
        if (Token->Type != EScriptTokenType::Class
            && Token->Type != EScriptTokenType::Struct
            && Token->Type != EScriptTokenType::Interface
        )
        {
            Context->RaiseError(10011,Token, U"Expect 'class'/'interface'/'struct'  here");
            return false;
        }
        Result->Class = Token;
        Context->CurrentTokenIndex++;
    }


    //read class name
    {
        auto Token = Context->GetCurrentToken();
        if (Token->Type != EScriptTokenType::Identifier)
        {
            Context->RaiseError(10012,Token, U"Expect class name here");
            return false; 
        }
        Result->Name = Token;
        Context->CurrentTokenIndex++;
    }

    //read generic parameters
    {
        auto Token = Context->GetCurrentToken();
        if (Token->Type == EScriptTokenType::Less)
        {
            Context->CurrentTokenIndex++; //skip '<'

            while (true)
            {
                auto GenericPlaceholderName = Context->GetCurrentToken();
                if (GenericPlaceholderName->Type != EScriptTokenType::Identifier)
                {
                    Context->RaiseError(10013,GenericPlaceholderName, U"Expect generic parameter name here");
                    return false;
                }
                Result->GenericPlaceholders.Add(GenericPlaceholderName);
                Context->CurrentTokenIndex++;

                auto NextToken = Context->GetCurrentToken();
                if (NextToken->Type == EScriptTokenType::Comma)
                {
                    Context->CurrentTokenIndex++; //skip comma
                }
                else
                {
                    break;
                }
            }

            auto RightToken = Context->GetCurrentToken();
            if (RightToken->Type != EScriptTokenType::Greater)
            {
                Context->RaiseError(10014,RightToken, U"Expect '>' here");
                return false;
            }
            Context->CurrentTokenIndex++; //skip '>'
        }

    }

    //read parent names
    {
        auto Token = Context->GetCurrentToken();
        if (Token->Type == EScriptTokenType::Colon)
        {
            Context->CurrentTokenIndex++; //skip colon

            while (true)
            {
                auto ParentName = Context->GetCurrentToken();
                if (ParentName->Type != EScriptTokenType::Identifier)
                {
                    Context->RaiseError(10015,ParentName, U"Expect parent name here");
                    return false;
                }
                Result->ParentNames.Add(ParentName);
                Context->CurrentTokenIndex++;

                auto NextToken = Context->GetCurrentToken();
                if (NextToken->Type == EScriptTokenType::Comma)
                {
                    Context->CurrentTokenIndex++; //skip comma
                }
                else
                {
                    break;
                }
            }
        }

    }

    //read left brace
    {
        auto Token = Context->GetCurrentToken();
        if (Token->Type != EScriptTokenType::LeftBrace)
        {
            Context->RaiseError(10016,Token, U"Expect '{' here");
            return false;
        }
        Context->CurrentTokenIndex++;
    }



    //read class content
    
    while (true)
    {
        auto Token = Context->GetCurrentToken();

        if(Token->Type== EScriptTokenType::EndOfFile)
        {
            //raise Error
            Context->RaiseError(10017,Token,U"unexptected end of file");
            return false;
        }


        if (Token->Type == EScriptTokenType::RightBrace)
        {
            Context->CurrentTokenIndex++;
            return true;    
        }

        //read access level
        std::shared_ptr<CAccessLevelNode> NextAccessLevel;
        if(Token->Type==EScriptTokenType::Public
        || Token->Type==EScriptTokenType::Protected
        || Token->Type==EScriptTokenType::Private
        || Token->Type==EScriptTokenType::Internal
        )
        {
            auto AccessLevelBuilder = std::make_shared<CAccessLevelBuilder>(Context);
            if (!AccessLevelBuilder->ReadCurentToken())
            {
                return false;
            }
            NextAccessLevel = AccessLevelBuilder->Result;

            Token = Context->GetCurrentToken();
        }


            //read enum keyword
        if (Token->Type == EScriptTokenType::Enum)
        {
            auto EnumBuilder = std::make_shared<CEnumBuilder>(Context);
            if (!EnumBuilder->ReadCurentToken())
            {
                return false;
            }
            auto EnumNode = EnumBuilder->Result;

            if (NextAccessLevel)
            {
                EnumNode->AccessLevel = NextAccessLevel;
            }

            Result->Enums.Add(EnumNode);
            continue;

        }

        //read delegate keyword
        if (Token->Type == EScriptTokenType::Delegate)
        {
            auto DelegateBuilder = std::make_shared<CDelegateBuilder>(Context);
            if (!DelegateBuilder->ReadCurentToken())
            {
                return false;
            }
            auto DelegateNode = DelegateBuilder->Result;

            if (NextAccessLevel)
            {
                DelegateNode->AccessLevel = NextAccessLevel;
            }

            Result->Delegates.Add(DelegateNode);
            continue;

        }

        //read decorator
        std::shared_ptr<CDecoratorNode> Decorator;
        if(Token->Type==EScriptTokenType::Static
        || Token->Type==EScriptTokenType::Virtual
        || Token->Type==EScriptTokenType::Override
        || Token->Type==EScriptTokenType::Abstract
        || Token->Type==EScriptTokenType::Const
        )
        {
            auto DecoratorBuilder=std::make_shared<CDecoratorBuilder>(Context);
            if(!DecoratorBuilder->ReadCurentToken())
            {
                return false;
            }
            Decorator=DecoratorBuilder->Result;

            Token = Context->GetCurrentToken();
        }




        //read class keyword
        if(Token->Type==EScriptTokenType::Class
        || Token->Type==EScriptTokenType::Struct
        || Token->Type==EScriptTokenType::Interface
        )
        {
            auto ClassBuilder=std::make_shared<CClassBuilder>(Context);
            if(!ClassBuilder->ReadCurentToken())
            {
                return false;
            }
            auto ClassNode=ClassBuilder->Result;

            if(NextAccessLevel)
            {
                ClassNode->AccessLevel=NextAccessLevel;
            }

            if(Decorator)
            {
                ClassNode->Decorator=Decorator;
            }
            Result->Classes.Add(ClassNode);
            continue;

        }

        //read a member 
        if(Token->Type==EScriptTokenType::Identifier
        || Token->Type==EScriptTokenType::InnerType
        )
        {
            //check if it is a constructor name
            if(Token->Type==EScriptTokenType::Identifier 
                && Token->ToString()==Result->Name->ToString()
                && Context->GetNextToken()->Type==EScriptTokenType::LeftParentheses
                )
            {
                //read constructor
                auto ConstructorBuilder=std::make_shared<CConstructorBuilder>(Context);

                if(NextAccessLevel)
                {
                    ConstructorBuilder->Result->AccessLevel=NextAccessLevel;
                }

                if(Decorator)
                {
                    ConstructorBuilder->Result->Decorator=Decorator;
                }

                if(!ConstructorBuilder->ReadCurentToken())
                {
                    return false;
                }

                auto ConstructorNode=ConstructorBuilder->Result;


                Result->Constructors.Add(ConstructorNode);

                continue;

            }


            auto TypeBuilder=std::make_shared<CTypeBuilder>(Context);
            if(!TypeBuilder->ReadCurentToken())
            {
                return false;
            }
            auto TypeNode=TypeBuilder->Result;



            //read identifier
            auto MemberName=Context->GetCurrentToken();
            {
                if(MemberName->Type == EScriptTokenType::Operator)
                {
                    //this is a operator
                    auto OperatorBuilder=std::make_shared<COperatorFunctionBuilder>(Context);
                    if(!OperatorBuilder->ReadCurentToken())
                    {
                        return false;
                    }
                    auto OperatorFunction=OperatorBuilder->Result;

                    if(NextAccessLevel)
                    {
                        OperatorFunction->AccessLevel=NextAccessLevel;
                    }

                    if(Decorator)
                    {
                        OperatorFunction->Decorator=Decorator;
                    }

                    OperatorFunction->ReturnType=TypeNode;

                    Result->OperatorFunctions.Add(OperatorFunction);
                    continue;


                }
                else if(MemberName->Type == EScriptTokenType::This)
                {
                    //this is a indexer
                    auto IndexerBuilder=std::make_shared<CPropertyBuilder>(Context);
                    if(!IndexerBuilder->ReadCurentToken())
                    {
                        return false;
                    }
                    auto IndexerPropertyNode=IndexerBuilder->Result;

                    if(NextAccessLevel)
                    {
                        IndexerPropertyNode->AccessLevel=NextAccessLevel;
                    }

                    if(Decorator)
                    {
                        IndexerPropertyNode->Decorator=Decorator;
                    }

                    IndexerPropertyNode->Type=TypeNode;

                    Result->Properties.Add(IndexerPropertyNode);

                    continue;
                }

                if (MemberName->Type != EScriptTokenType::Identifier)
                {
                    Context->RaiseError(10018,MemberName, U"Expect identifier here,maybe forget write return type");
                    return false;
                }

            }

            //check '('
            auto NextToken=Context->GetNextToken();
            if(NextToken->Type==EScriptTokenType::LeftParentheses)
            {
                //read function
                auto FunctionBuilder=std::make_shared<CFunctionBuilder>(Context);
                if(!FunctionBuilder->ReadCurentToken())
                {
                    return false;
                }
                auto FunctionNode=FunctionBuilder->Result;

                if(NextAccessLevel)
                {
                    FunctionNode->AccessLevel=NextAccessLevel;
                }

                if(Decorator)
                {
                    FunctionNode->Decorator=Decorator;
                }

                FunctionNode->ReturnType=TypeNode;

                Result->Functions.Add(FunctionNode);
                continue;
            }
            else  if(NextToken->Type == EScriptTokenType::LambdaOperator
                    || NextToken->Type == EScriptTokenType::LeftBrace
                    )
            {
                    //read property
                    auto PropertyBuilder=std::make_shared<CPropertyBuilder>(Context);
                    if(!PropertyBuilder->ReadCurentToken())
                    {
                        return false;
                    }
                    auto PropertyNode=PropertyBuilder->Result;

                    if(NextAccessLevel)
                    {
                        PropertyNode->AccessLevel=NextAccessLevel;
                    }

                    if(Decorator)
                    {
                        PropertyNode->Decorator=Decorator;
                    }

                    PropertyNode->Type=TypeNode;

                    Result->Properties.Add(PropertyNode);

                    continue;


            }
            else if( !Result->IsInterface() && 
                (NextToken->Type == EScriptTokenType::Semicolon|| NextToken->Type == EScriptTokenType::Assign)
            )
            {
                //read a field
                auto FieldBuilder=std::make_shared<CGSharpFieldBuilder>(Context);
                if(!FieldBuilder->ReadCurentToken())
                {
                    return false;
                }
                auto FieldNode=FieldBuilder->Result;

                if(NextAccessLevel)
                {
                    FieldNode->AccessLevel=NextAccessLevel;
                }

                if(Decorator)
                {
                    FieldNode->Decorator=Decorator;
                }

                FieldNode->Type=TypeNode;

                Result->Fields.Add(FieldNode);

                continue;


            }
            else
            {
                Context->RaiseError(10019,Token, U"Expect ';' or '=>' or '{' or '=' before token");
                return false;
            }
            

        }
        
        Context->RaiseError(10020,Token, U"unkown token  in class body");
        return false;


    }
    
    

}